package org.finesys.common.netty.websocket.stomp.support;

import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.stomp.StompFrame;
import io.netty.util.Attribute;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.finesys.common.netty.websocket.resolver.PathVariableMapMethodArgumentResolver;
import org.finesys.common.netty.websocket.resolver.PathVariableMethodArgumentResolver;
import org.finesys.common.netty.websocket.support.MethodArgumentResolver;
import org.finesys.common.netty.websocket.support.NettySession;
import org.finesys.common.netty.websocket.support.WebSocketEndpointConfig;
import org.finesys.common.netty.websocket.support.WsPathMatcher;
import org.finesys.common.netty.websocket.wrapper.AntPathMatcherWrapper;
import org.finesys.common.netty.websocket.wrapper.DefaultPathMatcherWrapper;
import org.springframework.beans.TypeMismatchException;

import java.lang.reflect.Method;
import java.util.*;

import static org.finesys.common.netty.websocket.constants.WebSocketAttributeKeyConstants.*;

public class StompWebSocketEndpointEventServer {

    /**
     * 路径和端点方法映射
     */
    private final Map<String, StompWebSocketEndpointMethodMapping> pathMethodMappingMap = new HashMap<>();

    /**
     * WebSocket配置
     */
    private final WebSocketEndpointConfig config;

    /**
     * WebSocket path matcher
     */
    private final Set<WsPathMatcher> pathMatchers = new HashSet<>();

    private static final InternalLogger INTERNAL_LOGGER = InternalLoggerFactory.getInstance(StompWebSocketEndpointEventServer.class);

    public Set<WsPathMatcher> getPathMatchers() {
        return pathMatchers;
    }

    public StompWebSocketEndpointEventServer(StompWebSocketEndpointMethodMapping webSocketEndpointMethodMapping, WebSocketEndpointConfig config, String path) {
        //保存方法和路径的映射
        addPathMethodMapping(path, webSocketEndpointMethodMapping);
        this.config = config;
    }

    /**
     * 向映射表中添加路径与WebSocket端点方法映射关系。
     *
     * @param path                           路径
     * @param webSocketEndpointMethodMapping WebSocket端点方法映射对象
     */
    public void addPathMethodMapping(String path, StompWebSocketEndpointMethodMapping webSocketEndpointMethodMapping) {
        // 将路径和WebSocket端点方法映射关系添加到映射表中
        pathMethodMappingMap.put(path, webSocketEndpointMethodMapping);
        boolean addedMatcher = false;

        // 遍历WebSocket端点方法映射对象中的onOpen参数解析器
        for (MethodArgumentResolver onOpenResolver : webSocketEndpointMethodMapping.getOnOpenArgResolvers()) {
            // 判断参数解析器是否为PathVariableMethodArgumentResolver或PathVariableMapMethodArgumentResolver的实例
            if (onOpenResolver instanceof PathVariableMethodArgumentResolver
                    || onOpenResolver instanceof PathVariableMapMethodArgumentResolver) {
                // 如果是，则添加AntPathMatcherWrapper到pathMatchers中
                pathMatchers.add(new AntPathMatcherWrapper(path));
                addedMatcher = true;
                // 跳出循环
                break;
            }
        }

        // 如果没有添加任何PathMatcher
        if (!addedMatcher) {
            // 则添加DefaultPathMatcherWrapper到pathMatchers中
            pathMatchers.add(new DefaultPathMatcherWrapper(path));
        }
    }


    /**
     * 根据路径获取端点方法映射
     */
    private StompWebSocketEndpointMethodMapping getWebSocketEndpointMethodMapping(String path, Channel channel) {
        StompWebSocketEndpointMethodMapping methodMapping;
        if (pathMethodMappingMap.size() == 1) {
            methodMapping = pathMethodMappingMap.values().iterator().next();
        } else {
            Attribute<String> attrPath = channel.attr(PATH_KEY);
            attrPath.set(path);
            methodMapping = pathMethodMappingMap.get(path);
            if (methodMapping == null) {
                throw new RuntimeException("path " + path + " is not in pathMethodMappingMap ");
            }
        }
        return methodMapping;
    }

    /**
     * 获取端点方法映射
     */
    private StompWebSocketEndpointMethodMapping getWebSocketMethodMapping(Channel channel) {
        StompWebSocketEndpointMethodMapping methodMapping;
        if (pathMethodMappingMap.size() == 1) {
            methodMapping = pathMethodMappingMap.values().iterator().next();
        } else {
            Attribute<String> attrPath = channel.attr(PATH_KEY);
            String path = attrPath.get();
            methodMapping = pathMethodMappingMap.get(path);
            if (methodMapping == null) {
                throw new RuntimeException("path " + path + " is not in pathMethodMappingMap ");
            }
        }
        return methodMapping;
    }

    public String getHost() {
        return config.getHost();
    }

    public String getPort() {
        return config.getPort();
    }

    /**
     * 握手前是否执行回调方法
     */
    public boolean hasBeforeHandshake(Channel channel, String path) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketEndpointMethodMapping(path, channel);
        return !Objects.isNull(methodMapping.getBeforeHandshake());
    }

    /**
     * 执行握手前回调方法
     */
    public void doBeforeHandshake(Channel channel, StompFrame req, String path) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketEndpointMethodMapping(path, channel);
        Object implement;

        try {
            implement = methodMapping.getEndpointInstance();
        } catch (Exception e) {
            INTERNAL_LOGGER.error(e);
            return;
        }
        //维护session端点实例
        channel.attr(WEB_SOCKET_KEY).set(implement);
        NettySession nettySession = new NettySession(channel);
        channel.attr(SESSION_KEY).set(nettySession);
        Method beforeHandshake = methodMapping.getBeforeHandshake();
        if (Objects.isNull(beforeHandshake)) {
            return;
        }

        try {
            beforeHandshake.invoke(implement, methodMapping.getBeforeHandshakeArgs(channel, req));
        } catch (TypeMismatchException e) {
            throw e;
        } catch (Throwable throwable) {
            INTERNAL_LOGGER.error(throwable);
        }
    }

    /**
     * 连接建立成功回调方法。
     * <p>
     * 当WebSocket连接建立成功时，此方法会被调用。
     *
     * @param channel WebSocket通道对象。
     * @param req     WebSocket连接请求对象。
     * @param path    WebSocket连接路径。
     */

    public void doOnOpen(Channel channel, StompFrame req, String path) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketEndpointMethodMapping(path, channel);
        Object implement = channel.attr(WEB_SOCKET_KEY).get();
        if (Objects.isNull(implement)) {

            try {
                implement = methodMapping.getEndpointInstance();
                channel.attr(WEB_SOCKET_KEY).set(implement);
            } catch (Exception e) {
                INTERNAL_LOGGER.error(e);
                return;
            }
            NettySession nettySession = new NettySession(channel);
            channel.attr(SESSION_KEY).set(nettySession);
        }
        Method onOpenMethod = methodMapping.getOnOpen();
        if (Objects.isNull(onOpenMethod)) {
            return;
        }

        try {
            onOpenMethod.invoke(implement, methodMapping.getOnOpenArgs(channel, req));
        } catch (TypeMismatchException e) {
            throw e;
        } catch (Throwable throwable) {
            INTERNAL_LOGGER.error(throwable);
        }
    }

    /**
     * 连接关闭方法回调
     */
    public void doOnClose(Channel channel) {
        Attribute<String> attrPath = channel.attr(PATH_KEY);
        StompWebSocketEndpointMethodMapping methodMapping;

        if (pathMethodMappingMap.size() == 1) {
            methodMapping = pathMethodMappingMap.values().iterator().next();
        } else {
            String path = attrPath.get();
            methodMapping = pathMethodMappingMap.get(path);
            if (methodMapping == null) {
                return;
            }
        }
        if (Objects.isNull(methodMapping.getOnClose())) {
            return;
        }
        if (!channel.hasAttr(SESSION_KEY)) {
            return;
        }
        Object implement = channel.attr(WEB_SOCKET_KEY).get();
        try {
            methodMapping.getOnClose().invoke(implement, methodMapping.getOnCloseArgs(channel));
        } catch (TypeMismatchException e) {
            throw e;
        } catch (Throwable throwable) {
            INTERNAL_LOGGER.error(throwable);
        }
    }

    /**
     * 连接错误方法回调
     */
    public void doOnError(Channel channel, Throwable throwable) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketMethodMapping(channel);
        if (Objects.isNull(methodMapping.getOnError())) {
            return;
        }
        if (!channel.hasAttr(SESSION_KEY)) {
            return;
        }
        Object implement = channel.attr(WEB_SOCKET_KEY).get();

        try {
            methodMapping.getOnError().invoke(implement, methodMapping.getOnErrorArgs(channel, throwable));
        } catch (Throwable t) {
            INTERNAL_LOGGER.error(t);
        }
    }

    /**
     * 接收到字符串类型消息回调
     */
    public void doOnMessage(Channel channel, WebSocketFrame frame) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketMethodMapping(channel);
        if (Objects.isNull(methodMapping)) {
            return;
        }
        TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) frame;
        Object implement = channel.attr(WEB_SOCKET_KEY).get();

        try {
            methodMapping.getOnMessage().invoke(implement, methodMapping.getOnMessageArgs(channel, textWebSocketFrame));
        } catch (Throwable t) {
            INTERNAL_LOGGER.error(t);
        }
    }

    /**
     * 接收二进制消息回调方法
     */
    public void doOnBinary(Channel channel, WebSocketFrame frame) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketMethodMapping(channel);
        if (Objects.isNull(methodMapping.getOnBinary())) {
            return;
        }
        BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame;
        Object implement = channel.attr(WEB_SOCKET_KEY).get();
        try {
            methodMapping.getOnBinary().invoke(implement, methodMapping.getOnBinaryArgs(channel, binaryWebSocketFrame));
        } catch (Throwable t) {
            INTERNAL_LOGGER.error(t);
        }

    }

    /**
     * netty事件方法回调
     */
    public void doOnEvent(Channel channel, Object evt) {
        StompWebSocketEndpointMethodMapping methodMapping = getWebSocketMethodMapping(channel);
        if (Objects.isNull(methodMapping.getOnEvent())) {
            return;
        }
        if (!channel.hasAttr(SESSION_KEY)) {
            return;
        }
        Object implement = channel.attr(WEB_SOCKET_KEY).get();
        try {
            methodMapping.getOnEvent().invoke(implement, methodMapping.getOnEventArgs(channel, evt));
        } catch (Throwable t) {
            INTERNAL_LOGGER.error(t);
        }
    }
}
